/*
 * Project: a2
 * Copyright (C) 2008 ralfoide gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package com.alfray.a2.gameplay;

import android.util.Log;

import com.alfray.a2.engine.BaseThread;
import com.alfray.a2.engine.CircularBuffer;


class ActionThread extends BaseThread {

    public interface IActionHandler {
        public void onActionEvent(Object object);
    }

    private static final boolean DEBUG = false;

    public static final int FPS = 30;
    private static final int FPS_MS = 1000/FPS;
    private static final String TAG = "ActionThread";

    protected final GameState mGameState;

    private long mLastDeltaTimeMs;
    private CircularBuffer<Action> mActions;


    public ActionThread(IActionHandler actionHandler, GameState gameState) {
        super("ActionThread");
        mGameState = gameState;

        mActions = new CircularBuffer<Action>(
                        new Action(actionHandler, gameState), 500);

        this.setPriority(Thread.currentThread().getPriority() - 1);
    }

    public float getEstimatedFps() {
        if (mLastDeltaTimeMs > 0) {
            return 1000.0f / mLastDeltaTimeMs;
        }
        return 0;
    }

    @Override
    public void run() {

        while (mContinue) {

            if (pauseWait() && !mContinue) break;

            long startMs = System.currentTimeMillis();

            processEvent(startMs);

            long waitMs = FPS_MS - (System.currentTimeMillis() - startMs);
            if (waitMs > 0) {
                try {
                    sleep(waitMs);
                } catch (InterruptedException e) {
                    // pass
                }
            }

            mLastDeltaTimeMs = System.currentTimeMillis() - startMs;
        }
    }

    private void processEvent(long startMs) {
        Action action;
        synchronized(mActions) {
            action = mActions.peek();

            if (action == null || action.mTimestamp > startMs) {
                return;
            }

            action = mActions.pop();
        }

        action.mOpcode.exec(action);
        mActions.recycle(action);
    }

    //----- Action ----

    private static class Action implements CircularBuffer.IElement {

        public GameState mGameState;
        public IActionHandler mActionHandler;
        public Opcode mOpcode;
        public long mTimestamp;
        public Object mEvent;

        public Action(IActionHandler actionHandler, GameState gameState) {
            mActionHandler = actionHandler;
            mGameState = gameState;
        }

        public void clear() {
            mOpcode = null;
            mTimestamp = 0;
            mEvent = 0;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Action clone() {
            Action a = new Action(this.mActionHandler, this.mGameState);
            a.clear();
            return a;
        }

        @SuppressWarnings("unchecked")
        public Action[] mkArray(int size) {
            return new Action[size];
        }
    }

    //----- Opcode ----

    private enum Opcode {

        /** Parameters: time_ms */
        WAIT {
            @Override
            public void exec(Action a) {
                // handled in the main loop, nothing here
            }
        },

        /** Parameters: gameplay, event_id */
        ACTION_EVENT {
            @Override
            public void exec(Action a) {
                if (DEBUG) Log.d(TAG, "Exec GameplayEvent");
                a.mActionHandler.onActionEvent(a.mEvent);
            }
        };

        public abstract void exec(Action a);
    }

    //----- Local Methods ----


    /** Queues an action to wait for the given <em>relative</em>
     * time expressed related to System.currentTimeMillis(). */
    public synchronized void queueDelayFor(long delayMs) {
        if (DEBUG) Log.d(TAG, "Queue Wait");
        synchronized (mActions) {
            Action a = mActions.push();
            a.mOpcode = Opcode.WAIT;
            a.mTimestamp = System.currentTimeMillis() + delayMs;
        }
    }

    /** Queues an action to generate an onActioEvent with the given event id. */
    public synchronized void queueEvent(Object eventId) {
        if (DEBUG) Log.d(TAG, "Queue GameplayEvent");
        synchronized (mActions) {
            Action a = mActions.push();
            a.mOpcode = Opcode.ACTION_EVENT;
            a.mEvent = eventId;
        }
    }

}
